package top.lishuoboy.spring.jdbc_template;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import top.lishuoboy.dependency.base.bean.User;
import top.lishuoboy.dependency.base.db.MyDbUtil;
import top.lishuoboy.dependency.base.json.HuJsonUtil;
import top.lishuoboy.spring.jdbc_template.service.UserService;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Slf4j
@ComponentScan(basePackageClasses = JdbcTemplateTest.class)
public class JdbcTemplateTest {
    private static ApplicationContext context = new AnnotationConfigApplicationContext(JdbcTemplateTest.class);

    /** 23.JdbcTemplate开发环境搭建测试 */
    @Test
    public void JdbcTemplateEnvBuildTest() {
        log.warn("{}", context.getBean(JdbcTemplateTest.class));

        /**
         * 输出结果
         * top.lishuoboy.spring.jdbc_template.JdbcTemplateTest$$EnhancerBySpringCGLIB$$5076e8bb@2663e964
         */
    }

    /** 24.整合 h2 数据库、hutool 连接池、JdbcTemplate */
    @Test
    public void dataSourcePool() throws SQLException {
        DataSource ds = context.getBean(DataSource.class);
        Connection conn = ds.getConnection();  // 这一步会创建数据库文件
        log.warn("ds\t=={}", ds);
        log.warn("conn\t=={}", conn);
        log.warn("ds\t={}", HuJsonUtil.toJsonPrettyStr(ds));

        /**
         * 输出结果
         * ds	==cn.hutool.db.ds.pooled.PooledDataSource@71329995
         * conn	==cn.hutool.db.ds.pooled.PooledConnection@13e3c1c7
         * ds	=
         * {
         *     "config":
         *     {
         *         "connProps": null,
         *         "driver": "org.h2.Driver",
         *         "initialSize": 0,
         *         "maxActive": 100,
         *         "maxWait": 0,
         *         "minIdle": 0,
         *         "pass": "1234",
         *         "url": "jdbc:h2:file:D:\\workspace\\lishuoboy-spring\\lishuoboy-spring-z1-jdbc_template/db/lishuoboy-spring",
         *         "user": "root"
         *     }
         * }
         */
    }

    /** 24.整合 h2 数据库、hutool 连接池、JdbcTemplate */
    @Test
    public void jdbcTemplate() {
        JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class);
        log.warn("jdbcTemplate=={}", jdbcTemplate);
        Integer integer = jdbcTemplate.queryForObject("select 1+2 from dual", Integer.class);
        log.warn("integer=={}", integer);

        /**
         * 输出结果
         * jdbcTemplate=org.springframework.jdbc.core.JdbcTemplate@632ceb35
         * integer==3
         */
    }

    /**
     * 25.重置数据库，初始化 db 数据
     */
    @Test
    @SneakyThrows
    public void resetDbData() {
        MyDbUtil.resetData(context.getBean(DataSource.class), "db_sql/lishuoboy-spring.sql");
    }

    /**
     * 26.JdbcTemplate 查询
     */
    @Test
    public void jdbcTemplateQuery() {
        resetDbData();

        final String sql = "select name from USER limit ?";
        JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class);

        // 1 查询一个值
        String name = jdbcTemplate.queryForObject(sql, String.class, 1);
        // 2 查询一条记录
        User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), 1);
        Map<String, Object> map = jdbcTemplate.queryForMap(sql, 1);
        // 3 查询多条记录
        List<User> userList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<User>(User.class), 5);
        List<Map<String, Object>> mapList = jdbcTemplate.queryForList(sql, 5);

        log.warn("name=={}", name);
        log.warn("user=={}", user);
        log.warn("map=={}", map);
        log.warn("userList=={}", userList);
        log.warn("mapList=={}", mapList);

        /**
         * 输出结果
         * name==张三
         * user==User(id=null, name=张三, pwd=null)
         * map=={NAME=张三}
         * userList==[User(id=null, name=张三, pwd=null), User(id=null, name=李四, pwd=null)]
         * mapList==[{NAME=张三}, {NAME=李四}]
         */
    }

    /**
     * 27.JdbcTemplate 增删改
     */
    @Test
    public void jdbcTemplateUpdate() {
        resetDbData();

        String sql;
        JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class);

        sql = "delete from user where id = ?";
        int deleteCount = jdbcTemplate.update(sql, 1);
        sql = "update user set name = ? where id = ?";
        int updateCount = jdbcTemplate.update(sql, new Object[]{"李四222", 2});
        sql = "insert into user values(?, ?, ?)";
        int insertCount = jdbcTemplate.update(sql, new Object[]{3, "王五", "333"});
        log.warn("deleteCount=={}", deleteCount);
        log.warn("updateCount=={}", updateCount);
        log.warn("insertCount=={}", insertCount);

        sql = "select * from USER";
        List<User> userList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
        log.warn("userList=={}", userList);

        /**
         * 输出结果
         * deleteCount==1
         * updateCount==1
         * insertCount==1
         *
         * userList==[User(id=2, name=李四222, pwd=null), User(id=3, name=王五, pwd=null)]
         */
    }

    /**
     * 28.JdbcTemplate 批量增删改
     */
    @Test
    public void jdbcTemplateBatchUpdate() {
        resetDbData();

        String sql;
        JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class);
        jdbcTemplate.update("truncate table user");

        sql = "insert into user values(?, ?, ?)";
        List<Object[]> argList = new ArrayList();
        for (int i = 1; i <= 1000; i++) {
            argList.add(new Object[]{i, "张三" + i, i});
        }
        int[] insertCount = jdbcTemplate.batchUpdate(sql, argList);
        log.warn("insertCount=={}", insertCount);

        /**
         * 输出结果
         * insertCount==[1, 1, 1,………………,1]
         */
    }

    /**
     * 29.事务初步。使用 @Transactional、@EnableTransactionManagement 开启事务
     * 原理：通过 AOP 动态代理简化了  UserService.transferMoneyCode() 编程式事务管理代码
     */
    @Test
    public void transactional() {
        resetDbData();

        UserService userService = context.getBean(UserService.class);
        userService.transferMoney(10, 1, 2);
    }

    /**
     * 30.编程式事务管理 和 @Transactional 注解原理
     * 原理：通过 AOP 动态代理简化了  UserService.transferMoneyCode() 编程式事务管理代码
     */
    @Test
    public void transactionalCode() {
        resetDbData();

        UserService userService = context.getBean(UserService.class);
        userService.transferMoneyCode(10, 1, 2);
    }

    /**
     * 31.事务传播行为 和 @Transactional 的属性
     *
     * REQUIRED	        如果当前存在事务，则加入该事务；如果当前没有事务，则创建一个新的事务。这是默认值
     * REQUIRES_NEW	    创建一个新的事务，如果当前存在事务，则把当前事务挂起
     * SUPPORTS	        如果当前存在事务，则加入该事务；如果当前没有事务，则以非事务的方式继续运行
     * NOT_SUPPORTED	以非事务方式运行，如果当前存在事务，则把当前事务挂起
     * NEVER	        以非事务方式运行，如果当前存在事务，则抛出异常
     * MANDATORY	    如果当前存在事务，则加入该事务；如果当前没有事务，则抛出异常
     * NESTED	        如果当前存在事务，则创建一个事务作为当前事务的嵌套事务来运行；如果当前没有事务，则该取值等价于 TransactionDefinition.PROPAGATION_REQUIRED
     *
     * @Transactional 的属性
     * 属性	                        类型	                            描述
     * value/transactionManager     String	                        事务管理器设置
     * propagation	                enum:Propagation	            可选的事务传播行为设置，默认REQUIRED
     * isolation	                enum:Isolation	                可选的事务隔离级别设置，默认为DEFAULT
     * timeout/timeoutString        int	                            事务超时时间设置，默认是 -1 秒
     * readOnly	                    boolean	                        读写或只读事物，默认false
     * rollbackFor	                Class<? extends Throwable>[]	导致事物回滚的异常数组。默认情况下，事务将在RuntimeException和Error上回滚，但不会在已检查异常（业务异常）上回滚。
     * rollbackForClassName	        String[]	                    导致事物回滚的类名数组
     * noRollbackFor	            Class<? extends Throwable>[]	不会导致事物回滚的异常数组
     * noRollbackForClassName	    String[]	                    不会导致事物回滚的类名数组
     */
    @Test
    public void txPropagation() {
        resetDbData();

        UserService userService = context.getBean(UserService.class);
        userService.transferMoney(10, 1, 2);
    }
}